
using System;
using System.ComponentModel;
using System.Data.Common;
using System.Data;
using System.Collections.Generic;
using Microsoft.Practices.Common;
using System.ComponentModel.Design;
using System.Configuration;
using Microsoft.Practices.RecipeFramework.VisualStudio;
using System.IO;
using System.Collections.Specialized;
using System.Globalization;

namespace Microsoft.Practices.RepositoryFactory.Converters
{
    /// <summary>
    /// Converts a Table Name
    /// </summary>
	public class TableNameConverter : StringConverter, IAttributesConfigurable
	{
		/// <summary>
		/// Configuration attribute that must be specified at the converter element 
		/// that determines the connection settings to use to retrieve the list of 
		/// available tables. The argument must have the name 
		/// "ConnectionSettingArgument" and point to an argument defined in 
		/// the containing recipe. Optionally, <see cref="ConnectionStringArgument"/> and 
		/// <see cref="ProviderArgument"/> can be specified.
		/// </summary>
		public const string ConnectionSettingsArgument = "ConnectionSettingsArgument";

		/// <summary>
		/// A full connection string to use to query for the table list. The argument must 
		/// have the name "ConnectionSettingArgument" and point to an argument defined in 
		/// the containing recipe. Either this or <see cref="ConnectionSettingsArgument"/> 
		/// must be specified.
		/// </summary>
		public const string ConnectionStringArgument = "ConnectionStringArgument";

		/// <summary>
		/// The data provider associated with the <see cref="ConnectionStringArgument"/> to use 
		/// to connect to the database. The argument must 
		/// have the name "ProviderArgument" and point to an argument defined in 
		/// the containing recipe. 
		/// </summary>
		public const string ProviderArgument = "ProviderArgument";

		StringDictionary configuration;

        /// <summary>
        /// Returns whether this object supports a standard set of values that can be picked from a list, using the specified context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context.</param>
        /// <returns>
        /// true if <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"></see> should be called to find a common set of values the object supports; otherwise, false.
        /// </returns>
		public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
		{
			return true;
		}

        /// <summary>
        /// Returns whether the collection of standard values returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"></see> is an exclusive list of possible values, using the specified context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context.</param>
        /// <returns>
        /// true if the <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"></see> returned from <see cref="M:System.ComponentModel.TypeConverter.GetStandardValues"></see> is an exhaustive list of possible values; false if other values are possible.
        /// </returns>
		public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
		{
			return true;
		}

        /// <summary>
        /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context.
        /// </summary>
        /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null.</param>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection"></see> that holds a standard set of valid values, or null if the data type does not support a standard set of values.
        /// </returns>
		public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
		{
			IDictionaryService dictionary = (IDictionaryService)context.GetService(typeof(IDictionaryService));

			string cnstring;

			DbProviderFactory factory;
			try
			{
				string dbprovider;
				if (configuration.ContainsKey(ConnectionSettingsArgument))
				{
					ConnectionStringSettings settings = (ConnectionStringSettings)dictionary.GetValue(
						configuration[ConnectionSettingsArgument]);
					dbprovider = settings.ProviderName;
					cnstring = settings.ConnectionString;
				}
				else
				{
					dbprovider = (string)dictionary.GetValue(configuration[ProviderArgument]);
					cnstring = (string)dictionary.GetValue(configuration[ConnectionStringArgument]);
				}

				factory = DbProviderFactories.GetFactory(dbprovider);
			}
			catch
			{
				return new StandardValuesCollection(null);
			}

			System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
			DbConnection cn = factory.CreateConnection();
			try
			{
				cn.ConnectionString = cnstring;
				cn.Open();
				// Get table names
				// See http://www.daveandal.net/articles/schema-api/default.htm for more information 
				// and tools for the new Schema API in ADO.NET v2.0

				/* Restrictions for the GetSchema on Tables
				CollectionName RestrictionName RestrictionDefault RestrictionNumber 
				--------------------------------------------------------------------
				Tables			Catalog			TABLE_CATALOG		1 
				Tables			Owner			TABLE_SCHEMA		2 
				Tables			Table			TABLE_NAME			3 
				Tables			TableType		TABLE_TYPE			4 
				*/
				DataTable table = cn.GetSchema("Tables",
					new string[] { null, null, null, "BASE TABLE" });
				List<string> tables = new List<string>(table.Rows.Count);
				foreach (DataRow row in table.Rows)
				{
					tables.Add((string)row["TABLE_NAME"]);
				}
                tables.Sort(StringComparer.CurrentCulture);
				return new StandardValuesCollection(tables);
			}
			catch
			{
				return new StandardValuesCollection(null);
			}
			finally
			{
				cn.Dispose();
				System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
			}
		}

		#region IAttributesConfigurable Members

		/// <summary>
		/// Receives the name of the configuration setting to use to retrieve the list of tables.
		/// </summary>
		public void Configure(System.Collections.Specialized.StringDictionary attributes)
		{
            if (attributes == null)
                throw new ArgumentNullException("attributes");
            
            if (!attributes.ContainsKey(ConnectionSettingsArgument))
			{
				if (!attributes.ContainsKey(ConnectionStringArgument) && !attributes.ContainsKey(ProviderArgument))
				{
					throw new ArgumentException(
                        string.Format(CultureInfo.CurrentCulture, Properties.Resources.TableConfigureError, 
                        ConnectionSettingsArgument, 
                        CultureInfo.CurrentCulture.TextInfo.ListSeparator, ConnectionStringArgument));
				}
			}
			this.configuration = attributes;
		}

		#endregion
	}
}
